/* xxxio.c
   By: Spencer Clark
   Date created: March 19th 2011
   Date modified: March 19th 2011
   Modified by: Spencer Clark
*/

#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>

#include <xxxio.h>

#define MALLOC_SIZE 102400

char nl[] = "\n";
char tab[] = "\t";

int cur_char_column = 0;
char *vid_mem_address;

typedef struct _print_chunk {
  char* source;
  int startindex;
  int stopindex;
  struct _print_chunk* next;
} print_chunk;

int malloc_index = 0;
char malloc_bytes[MALLOC_SIZE];

void* xmalloc(size_t size)
{
  if ((malloc_index + size) >= MALLOC_SIZE) {
    return NULL;
  }
  void* chunk = &malloc_bytes[malloc_index];
  malloc_index += size;
  return chunk;
}

void xxxio_init(void)
{
  int i;
  unsigned char* cursor_register = (void*)0x0a;
  *cursor_register = 0x00; /* disable the cursor */
  clear_screen();
  for(i=0; i < MALLOC_SIZE; i++) {
    malloc_bytes[i] = 0;
  }
}

void format_int(print_chunk*, int);
void format_hex(print_chunk*, uint32_t);
void format_uint(print_chunk*, uint32_t);
void output_chunks(const uint8_t, print_chunk*);

/*
 * Post: Chunk points to a print_chunk that has as its source a null-terminated
 *       string with startindex = 0 and stopindex = n-1 where n is the length
 *       of the string including the null terminator.
 */
void insert_format(print_chunk* chunk, char fmt, va_list* args);
void insert_special(print_chunk* chunk, char spc);

void xprintf_work(const uint8_t colour, char* format, va_list args)
{
  int i;
  print_chunk* start;
  print_chunk* chunk;
  char c;

  start = xmalloc(sizeof (print_chunk));
  chunk = start;
  chunk->source = format;
  chunk->startindex = 0;
  chunk->stopindex = 0;
  chunk->next = NULL;
  for (i=0, c=format[i]; c != '\0'; c=format[++i], chunk->stopindex++) {
    if (c == '%' || c == '\\') {
      if (format[i+1] == c) { /* replace double-control with one char */
        chunk->stopindex = i+1;
      } else {
        chunk->next = xmalloc(sizeof (print_chunk));
        chunk = chunk->next;
        switch (c) {
          case '%': /* format data */
            insert_format(chunk, format[i+1], &args);
            break;
          case '\\': /* special character */
            insert_special(chunk, format[i+1]);
            break;
        }
      }
      i += 2; /* skip over the handled characters */
      /* return to format string after printing format controls */
      chunk->next = xmalloc(sizeof (print_chunk));
      chunk = chunk->next;
      chunk->source = format;
      chunk->startindex = i;
      chunk->stopindex = i;
    }
  }
  chunk->stopindex++;
  output_chunks(colour, start);
}

void insert_format(print_chunk* chunk, char fmt, va_list* args)
{
  int input;
  uint32_t inputhex;
  uint32_t inputuint;
  switch (fmt) {
    case 'i': /* integer */
      input = va_arg(*args, int);
      format_int(chunk, input);
      break;
    case 'u': /* unsigned integer */
      inputuint = va_arg(*args, uint32_t);
      format_uint(chunk, inputuint);
      break;
    case 'x': /* hex format uint */
      inputhex = va_arg(*args, uint32_t);
      format_hex(chunk, inputhex);
      break;
    default: /* unrecognized. print nothing */
      chunk->source = NULL;
  }
}

void insert_special(print_chunk* chunk, char spc)
{
  switch (spc) {
    case 'n': /* newline */
      chunk->source = nl;
      break;
    case 't': /* tab */
      chunk->source = tab;
      break;
    default: /* unrecognized. print nothing */
      chunk->source = NULL;
  }
  chunk->startindex = 0;
  chunk->stopindex = 1;
}

void xprintf_c(const uint8_t colour, char* format, ...)
{
  va_list args;
  /* prepare the variable list of arguments and pass to printf work procedure */
  va_start(args, format);
  xprintf_work(colour, format, args);
  va_end(args);
}

void xprintf(char* format, ...)
{
  va_list args;
  /* prepare the variable list of arguments and pass to printf work procedure */
  va_start(args, format);
  xprintf_work(DEFAULT_TEXT_COLOUR, format, args);
  va_end(args);
}

int xstrlen(char* s)
{
  int i;
  for (i=0; s[i] != 0; i++) {}
  return i;
}

/* reverse:  reverse string s in place */
void reverse(char s[])
{
  int i;
  int j;
  char c;

  for (i = 0, j = xstrlen(s)-1; i<j; i++, j--) {
    c = s[i];
    s[i] = s[j];
    s[j] = c;
  }
}

/* itoa:  convert n to characters in s */
void itoa(int n, char s[])
{
  int i;
  int sign;

  /* save sign and make n positive */
  if ((sign = n) < 0) {
    n = -n; 
  }
  i = 0;
  do {       /* generate digits in reverse order */
    s[i++] = n % 10 + '0';   /* get next digit */
  } while ((n /= 10) > 0);   /* delete it */
  if (sign < 0) {
    s[i++] = '-';
  }
  s[i] = '\0';
  reverse(s);
}

int digits(int n)
{
  int i;
  bool neg = n < 0 ? true : false;
  if(!n) {
    return 1;
  }
  for(i = 0; n; ++i) {
    n /= 10;
  }
  if (n < 0) {
    i++;
  }
  if (neg) {
    i++;
  }
  return i;
}

void format_int(print_chunk* chunk, int n)
{
  int ndigits = digits(n);
  chunk->source = xmalloc((ndigits+1) * sizeof (char));
  itoa(n, chunk->source);
  chunk->startindex = 0;
  chunk->stopindex = ndigits;
}

/* itoa:  convert n to characters in s */
void xtoa(uint32_t n, char s[])
{
  int i = 0;
  int sign;
  int digit;

  /* save sign and make n positive */
  if ((sign = n) < 0) {
    n = -n; 
  }
  i = 0;
  do { /* generate digits in reverse order */
    digit = n % 16;
    s[i++] = digit < 10 ? digit + '0' : digit % 10 + 'a';  /* get next digit */
  } while ((n /= 16) > 0);   /* delete it */
  if (sign < 0) {
    s[i++] = '-';
  }
  s[i] = '\0';
  reverse(s);
}

int xdigits(uint32_t n)
{
  int i;
  bool neg = n < 0 ? true : false;
  if(!n) {
    return 1;
  }
  for(i = 0; n; ++i) {
    n /= 16;
  }
  if (neg) {
    i++;
  }
  return i;
}

void format_hex(print_chunk* chunk, uint32_t n)
{
  int ndigits = xdigits(n);
  chunk->source = xmalloc((ndigits+1)* sizeof (char));
  xtoa(n, chunk->source);
  chunk->startindex = 0;
  chunk->stopindex = ndigits;
}

/* itoa:  convert n to characters in s */
void utoa(uint32_t n, char s[])
{
  int i = 0;
  do {       /* generate digits in reverse order */
    s[i++] = n % 10 + '0';   /* get next digit */
  } while ((n /= 10) > 0);   /* delete it */
  s[i] = '\0';
  reverse(s);
}

int udigits(uint32_t n)
{
  int i;
  if(!n) {
    return 1;
  }
  for(i = 0; n; ++i) {
    n /= 10;
  }
  return i;
}


void format_uint(print_chunk* chunk, uint32_t n)
{
  int ndigits = udigits(n);
  chunk->source = xmalloc((ndigits+1) * sizeof (char));
  utoa(n, chunk->source);
  chunk->startindex = 0;
  chunk->stopindex = ndigits;
}

void xprintc_c(uint8_t colour, const char c) {
  int i;

  /* allows for newlines to occur - prints spaces on rest of line */
  if(c == 0xA) {
    for(i = cur_char_column; i < 80; i++){
      vid_mem_address[0] = ' ';
      vid_mem_address[1] = colour;
      vid_mem_address += 2;
    }
    cur_char_column = 0;
  } else if (c == 0x9) { /* tab */
    for (i=0; i<5 && cur_char_column < 80; i++, cur_char_column++) {
      vid_mem_address[0] = ' ';
      vid_mem_address[1] = colour;
      vid_mem_address += 2;
    }
  } else if (c == 0x8) { /* backspace */
    vid_mem_address -= 2;
    vid_mem_address[0] = ' ';
    vid_mem_address[1] = colour;
    cur_char_column--;
  } else if (c != 0) {
    /* character */
    vid_mem_address[0] = c;
    /* format */
    vid_mem_address[1] = colour;
    vid_mem_address += 2;
    cur_char_column ++;
  }

  /* wrap if about to write to video memory off screen */
  if (vid_mem_address > 0xb8000 + 3998) {
    vid_mem_address = 0xb8000;
  }
  /* wrap lines at 80 chars */
  if (cur_char_column >= 80) {
    cur_char_column = 0;
  }
}

void xprintc(const char c) {
  xprintc_c(DEFAULT_TEXT_COLOUR, c);
}

void output_chunks(const uint8_t colour, print_chunk* chunk)
{
  int i;
  while (chunk != NULL) {
    if (chunk->source == NULL) {
      chunk = chunk->next;
      continue;
    }
    for (i = chunk->startindex; i < chunk->stopindex; i++) {
      xprintc_c(colour, (chunk->source)[i]);
    }
    chunk = chunk->next;
  }
}

void clear_screen(void)
{
  int i;
  vid_mem_address = 0xb8000;
  for(i=0; i < 4000; i += 2) {
    vid_mem_address[i] = ' ';
    vid_mem_address[i+1] = 0x00;
  }
  vid_mem_address = 0xb8000;
}

void printbomb(void) {
  char color = char_colour(RED, BLACK);
  xprintf_c(color, "\t\t\t              \\\\|/\n");
  xprintf_c(color, "\t\t\t             .-*-\n");
  xprintf_c(color, "\t\t\t            / /|\\\\ \n");
  xprintf_c(color, "\t\t\t           _L_\n");
  xprintf_c(color, "\t\t\t         ,\"   \".\n");          
  xprintf_c(color, "\t\t\t     (\\\\ /  O O  \\\\ /)\n");
  xprintf_c(color, "\t\t\t      \\\\|    _    |/\n");       
  xprintf_c(color, "\t\t\t        \\\\  (_)  /\n");        
  xprintf_c(color, "\t\t\t        _/.___,\\\\_\n");      
  xprintf_c(color, "\t\t\t       (_/     \\\\_)\n\n");
  xprintf_c(DEFAULT_TEXT_COLOUR, "\n");
}

